package ar.com.fiuba.ElectronicCircuitAnalyst.domain.impl.factory;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import ar.com.fiuba.ElectronicCircuitAnalyst.application.api.dto.CircuitDto;
import ar.com.fiuba.ElectronicCircuitAnalyst.application.api.dto.ConnectionDto;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.Circuit;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.CircuitElement;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.CircuitFactory;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.Connection;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.ConnectionFactory;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.Lead;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.repository.CircuitRepository;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.repository.ComponentRepository;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.common.Specification;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.exceptions.CircuitElementNotFoundException;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.exceptions.InvalidCircuitElementException;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.exceptions.LeadConnectionLimitReachedException;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.impl.CircuitImpl;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.impl.LeadImpl;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.specs.OutputLeadCanConnectSpecification;

public class CircuitFactoryImpl implements CircuitFactory {

	private Specification<Circuit> specification;

	private List<ComponentRepository> componentRepositories;

	private List<CircuitRepository> circuitRepositories;

	private ConnectionFactory connectionFactory;

	public CircuitFactoryImpl(Specification<Circuit> specification, List<ComponentRepository> componentRepositories,
			List<CircuitRepository> circuitRepositories, ConnectionFactory connectionFactory) {
		this.specification = specification;
		this.componentRepositories = componentRepositories;
		this.circuitRepositories = circuitRepositories;
		this.connectionFactory = connectionFactory;
	}

	public Circuit makeCircuit(CircuitDto data) throws InvalidCircuitElementException {
		String name = data.getName();
		
		List<Lead> inputs = makeLeads(data.getInputsCount(), new OutputLeadCanConnectSpecification());
		List<Lead> outputs = makeLeads(data.getOutputsCount(), new OutputLeadCanConnectSpecification());
		List<CircuitElement> circuitElements = makeCircuitElements(data.getComponentsNames());
		
		CircuitImpl theCircuit = new CircuitImpl(name, inputs, outputs, circuitElements, specification);

		List<Connection> connections = makeConnections(data.getConnections(), theCircuit, circuitElements);
		theCircuit.setConnections(connections);

		return theCircuit;
	}

	private List<Lead> makeLeads(Integer leadsCount, Specification<Lead> spec){
		List<Lead> leads = new ArrayList<Lead>();
		for (int i=0; i<leadsCount; i++){
			leads.add(new LeadImpl(spec));
		}
		return leads;
	}

	private List<CircuitElement> makeCircuitElements(List<String> componentsNames) throws InvalidCircuitElementException {
		List<CircuitElement> circuitElements = new ArrayList<CircuitElement>();

		for (String currentName : componentsNames) {
			CircuitElement currentCircuitElement = getCircuitElementFromRepositories(currentName);
			circuitElements.add(currentCircuitElement);
		}

		return circuitElements;
	}

	private List<Connection> makeConnections(List<ConnectionDto> connectionsData, Circuit mainCircuitElement,
			List<CircuitElement> elements) throws InvalidCircuitElementException {
		List<Connection> connections = new ArrayList<Connection>();

		List<CircuitElement> allCircuitElements = new ArrayList<CircuitElement>();
		allCircuitElements.add(mainCircuitElement);
		allCircuitElements.addAll(elements);

		for (ConnectionDto connectionData : connectionsData) {
			try {
				connections.add(connectionFactory.makeConnection(connectionData, allCircuitElements));
			} catch (CircuitElementNotFoundException e) {
				throw new InvalidCircuitElementException("Invalid connection: " + e.getMessage());
			}
			catch (LeadConnectionLimitReachedException e) {
					throw new InvalidCircuitElementException("Some lead reached connection limit: " + e.getMessage());
			}
		}

		return connections;
	}

	private CircuitElement getCircuitElementFromRepositories(String name) throws InvalidCircuitElementException {
		CircuitElement ret = null;
		Boolean circuitElementFound = false;
		Iterator<ComponentRepository> componentRepositoriesIterator = componentRepositories.iterator();

		while (componentRepositoriesIterator.hasNext() && !circuitElementFound) {
			ComponentRepository repository = componentRepositoriesIterator.next();
			circuitElementFound = true;
			try {
				ret = repository.getComponentByName(name);
			} catch (CircuitElementNotFoundException e) {
				circuitElementFound = false;
			}
		}

		Iterator<CircuitRepository> circuitRepositoriesIterator = circuitRepositories.iterator();

		while (circuitRepositoriesIterator.hasNext() && !circuitElementFound) {
			CircuitRepository repository = circuitRepositoriesIterator.next();
			circuitElementFound = true;
			try {
				ret = repository.getCircuitByName(name);
			} catch (CircuitElementNotFoundException e) {
				circuitElementFound = false;
			}
		}

		if (!circuitElementFound)
			throw new InvalidCircuitElementException("Circuit element with name \"" + name + "\" could not be found.");

		return ret;
	}
}
